/*
 * File Name: server.c
 *
 * Group Members: Cassie Chin (cassiechin9793@gmail.com)
 *                Stephen Smetana (stephensmetana11@gmail.com)
 *                Logan Widick (logan.widick@gmail.com)
 *
 * File Description: 
 *
 *
 * Sources: http://beej.us/guide/bgnet/examples/server.c
 *          This code is originally intended to send the string "Hello, World!\n" 
 *          out over a stream connection.
 *
 *          We took the outline of this code and incorporated the ability to 
 *          perform actions in a child process. This child process recieves
 *          HTTP requests from a client.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <stddef.h>
#include "httpstuff.h"

/* We can delete everything we don't need here before we turn in the project 
 Turn statement to ON to use any of these features */
#define DEBUG_OFF
#define SETSOCKOPT_OFF
#define REAP_DEAD_PROCESSES_OFF

#define PORT "60001"  // the port users will be connecting to
#define BACKLOG 10     // how many pending connections queue will hold
#define BUFFERSIZE 256

void main_child(int fd);

void sigchld_handler(int s) {
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*) sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*) sa)->sin6_addr);
}

int main(void) {
	int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
	struct addrinfo hints, *servinfo, *p;
	struct sockaddr_storage their_addr; // connector's address information
	socklen_t sin_size;
	struct sigaction sa;
	int yes = 1;
	char s[INET6_ADDRSTRLEN];
	int rv;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE; // use my IP

	if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}

	// loop through all the results and bind to the first we can
	for (p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
				== -1) {
			perror("server: socket");
			continue;
		}

#ifdef SETSOCKOPT_ON
		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
						sizeof(int)) == -1) {
			perror("setsockopt");
			exit(1);
		}
#endif

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("server: bind");
			continue;
		}

		break;
	}

	if (p == NULL) {
		fprintf(stderr, "server: failed to bind\n");
		return 2;
	}

	freeaddrinfo(servinfo); // all done with this structure

	if (listen(sockfd, BACKLOG) == -1) {
		perror("listen");
		exit(1);
	}

#ifdef REAP_DEAD_PROCESSES_ON
	sa.sa_handler = sigchld_handler; // reap all dead processes
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1) {
		perror("sigaction");
		exit(1);
	}
#endif

	printf("server: waiting for connections...\n");

	while (1) { // main accept() loop
		sin_size = sizeof their_addr;
		new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);
		if (new_fd == -1) {
			perror("accept");
			continue;
		}

		inet_ntop(their_addr.ss_family,
				get_in_addr((struct sockaddr *) &their_addr), s, sizeof s);
		printf("server: got connection from %s\n", s);

		/* BEGIN CHILD PROCESS : HTTP request processing is done here*/
		if (!fork()) {
			close(sockfd); // child doesn't need the listener
			main_child(new_fd);
			close(new_fd);
			exit(0);
		}
		/* END CHILD PROCESS */
		close(new_fd); // parent doesn't need this
	}

	return 0;
}

void main_child(int new_fd) {
	int bytesReceived = 0;
	char message[BUFFERSIZE + 1];
	//    struct sockaddr_in client_address;
	HTTPMsg *recv_msg = NULL;
	HTTPMsg *resp_msg = NULL;

	//try to open file descriptors
	FILE *sock_in_fp = fdopen(new_fd, "r");
	FILE *sock_out_fp = fdopen(dup(new_fd), "w");
	setvbuf(sock_in_fp, NULL, _IONBF, 0);
	setvbuf(sock_out_fp, NULL, _IONBF, 0);
	
	while (1) {

		// PARSE HTTP request here
		recv_msg = new_HTTPMsg();
		if (!recv_msg)
			break;
		recv_msg->isRequest = true;
		HTTPMsg_doParsing(recv_msg, sock_in_fp);

		//we should have reached either the BODY state or the END state.
		if (recv_msg->state != BODY && recv_msg->state != END) {
			printf("We have not reached BODY or END state. Closing session with the client.");
			delete_HTTPMsg(recv_msg);
			break;
		}

		//print out packet details
		printf("RECEIVED THE PACKET.\n");
		printf("Method is %s\n", recv_msg->method);
		printf("Resource is %s\n", recv_msg->resource);
		char *msg_buffer = HTTPMsg_findFilePath(recv_msg);
		if(msg_buffer != NULL) printf("Resource on file system is %s\n", msg_buffer);
		else printf("We do not have the resource\n");
		printf("HTTP Version is %f\n", recv_msg->version);

		printf("HEADERS BEGIN\n\n");
		HeaderListNode *hdr_lst_iter = recv_msg->headerList->head;
		for (; hdr_lst_iter != NULL; hdr_lst_iter = hdr_lst_iter->next) {
			printf("Header name: %s\n", hdr_lst_iter->name);
			printf("Header value: %s\n", hdr_lst_iter->value);
			fflush(stdout);
		}
		printf("\n\nHEADERS END\n");

		printf("BODY BEGIN\n");
		fflush(stdout);
		if (recv_msg->fileptr) {
			rewind(recv_msg->fileptr);
			int bytes_received = 0;
			while (1) {
				 bytes_received = fread(message, sizeof(char), BUFFERSIZE, recv_msg->fileptr);
				if(bytes_received <= 0) break;
				message[bytes_received] = '\0';
				printf("%s", message);
				if (feof(recv_msg->fileptr))
					break;
			}
		} else {
			printf("[No body available]");
		}
		printf("\n\nBODY END\n");

		resp_msg = new_HTTPMsg();

		//GET: return headers and body data
		if (strcmp(recv_msg->method, "GET") == 0) {
			printf("IT'S A GET\n");
			resp_msg->version = 1.0;

			//Could not parse resource requested; send 404.
			if(msg_buffer == NULL){
        resp_msg->statusCode = 404;
				resp_msg->fileptr = tmpfile();
				char tmp_body_buffer[BUFFER_SIZE+1];
				snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>404 Not Found</h1></html>\n");
				fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), resp_msg->fileptr);
				fflush(resp_msg->fileptr);				
				fseek(resp_msg->fileptr, 0, SEEK_END);
				resp_msg->contentLength = (int)ftell(resp_msg->fileptr);
				printf("And a 404\n");
				HTTPMsg_sendMsg(resp_msg, sock_out_fp);
        delete_HTTPMsg(recv_msg);
        delete_HTTPMsg(resp_msg);
        resp_msg = NULL;
        recv_msg = NULL;
        continue;
			}

			FILE* resp_file = fopen(msg_buffer, "r");
			//NULL file = 404
			if (resp_file == NULL) {
				resp_msg->statusCode = 404;
				resp_msg->fileptr = tmpfile();
				char tmp_body_buffer[BUFFER_SIZE+1];
				snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>404 Not Found</h1></html>\n");
				fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), resp_msg->fileptr);
				fflush(resp_msg->fileptr);				
				fseek(resp_msg->fileptr, 0, SEEK_END);
				resp_msg->contentLength = (int)ftell(resp_msg->fileptr);
				printf("And a 404\n");
				HTTPMsg_sendMsg(resp_msg, sock_out_fp);
			}

			//File with stuff = 200
			else {
				fseek(resp_file, 0, SEEK_END);
				resp_msg->contentLength = (int) ftell(resp_file);
        HeaderList_addHeader(resp_msg->headerList, "Content-Location", &(msg_buffer[1]));
				rewind(resp_file);
				resp_msg->fileptr = resp_file;
				resp_msg->statusCode = 200;
				printf("And a 200\n");
				HTTPMsg_sendMsg(resp_msg, sock_out_fp);
			}
		}
		//HEAD: return headers only.
		else if (strcmp(recv_msg->method, "HEAD") == 0) {
			resp_msg->version = 1.0;
			printf("IT'S A HEAD\n");
			
      //Could not parse resource requested; send 404.
			if(msg_buffer == NULL){
        resp_msg->statusCode = 404;
				resp_msg->fileptr = tmpfile();
				char tmp_body_buffer[BUFFER_SIZE+1];
				snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>404 Not Found</h1></html>\n");
				fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), resp_msg->fileptr);
				fflush(resp_msg->fileptr);				
				fseek(resp_msg->fileptr, 0, SEEK_END);
				resp_msg->contentLength = (int)ftell(resp_msg->fileptr);
				printf("And a 404\n");
				HTTPMsg_sendMsg(resp_msg, sock_out_fp);
        delete_HTTPMsg(recv_msg);
        delete_HTTPMsg(resp_msg);
        resp_msg = NULL;
        recv_msg = NULL;
        continue;
			}
         
      FILE* resp_file = fopen(msg_buffer, "r");

			//NULL file = 404
			if (resp_file == NULL) {
				resp_msg->statusCode = 404;
				printf("And a 404\n");
				resp_msg->fileptr = tmpfile();
				char tmp_body_buffer[BUFFER_SIZE+1];
				snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>404 Not Found</h1></html>\n");
				fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), resp_msg->fileptr);
				fflush(resp_msg->fileptr);
				fseek(resp_msg->fileptr, 0, SEEK_END);
				resp_msg->contentLength = (int)ftell(resp_msg->fileptr);				
				HTTPMsg_sendMsg(resp_msg, sock_out_fp);
			}

			//File with stuff = 200
			else {
				fseek(resp_file, 0, SEEK_END);
				resp_msg->contentLength = (int) ftell(resp_file);
        HeaderList_addHeader(resp_msg->headerList, "Content-Location", &(msg_buffer[1]));
				resp_msg->statusCode = 200;
				printf("And a 200\n");
				HTTPMsg_sendMsg(resp_msg, sock_out_fp);
				fclose(resp_file);
			}
		}

		//we do not implement the method; give 501 Not Implemented
		else {
			resp_msg->version = 1.0;
			printf("We do not implement %s\n", recv_msg->method);
			resp_msg->statusCode = 501;
			resp_msg->fileptr = tmpfile();
			char tmp_body_buffer[BUFFER_SIZE+1];
			snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>501 Not Implemented</h1></html>\n");
			fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), resp_msg->fileptr);
			fflush(resp_msg->fileptr);
			fseek(resp_msg->fileptr, 0, SEEK_END);
				resp_msg->contentLength = (int)ftell(resp_msg->fileptr);
			HTTPMsg_sendMsg(resp_msg, sock_out_fp);
		}

		//CLEANUP stuff here
		delete_HTTPMsg(recv_msg);
		delete_HTTPMsg(resp_msg);
		recv_msg = NULL;
		resp_msg = NULL;
	}

	//close file descriptors and exit
	fclose(sock_in_fp);
	fclose(sock_out_fp);
	exit(0);
}
